#pragma once

#include <iostream>
#include <string>

#include "arithmetic.h"
#include "location.h"
#include "types.h"
#include "utils.h"

namespace Token {
    enum class Tag {
        // Operators (0x000)
        LParen = '(',
        RParen = ')',
        LBracket = '[',
        RBracket = ']',
        LBrace = '{',
        RBrace = '}',
        Comma = ',',
        Semi = ';',

        Add = '+',
        Sub = '-',
        Mul = '*',
        Div = '/',
        Mod = '%',

        Assign = '=',
        Not = '!',
        Less = '<',
        Greater = '>',

        Equal = 'E',         // ==, E
        NotEqual = 'N',      // !=, N
        LessEqual = 'L',     // <=, L
        GreaterEqual = 'G',  // >=, G
        LogicAnd = '&',      // &&, &
        LogicOr = '|',       // ||, |

        // Keywords (0x100)
        If = 0x100,
        Else,
        While,
        Break,
        Continue,
        Return,

        VoidType = 0x180,
        IntType,
        FloatType,
        ConstQualifier,

        // Value (0x200)
        IntLiteral = 0x200,
        FloatLiteral,

        // Others (0x300)
        Identifier = 0x300,
        String,

        // Exception (0xf00)
        End = 0xf00,
    };

    struct BaseToken {
        Tag tag;
        Location loc;

        virtual void Print() const {}

        template <class T>
        T* As() {
            return dynamic_cast<T*>(this);
        }

      protected:
        BaseToken(Location loc, Tag tag) : loc(loc), tag(tag) {}
    };

    struct Operator : public BaseToken {
        Operator(Location loc, Tag tag) : BaseToken(loc, tag) {
            Utils::Assert(((int)tag & 0xf00) == 0x000, "not a valid tag");
        }
        bool isUnary() const {
            return tag == Tag::Add || tag == Tag::Sub || tag == Tag::Not;
        }
        void Print() const override { std::cout << "Operator: " << (char)tag; }
    };

    // Keyword - includes {if, else, while, break, continue, return}
    struct Keyword : public BaseToken {
        static Keyword* MakeKeyword(Location loc, Tag tag);

      protected:
        Keyword(Location loc, Tag tag) : BaseToken(loc, tag) {
            Utils::Assert(((int)tag & 0xf00) == 0x100, "not a valid tag");
        }
    };

    // TypeDecl - includes {const, void, int, float}
    struct TypeDecl : public Keyword {
        TypeDecl(Location loc, Tag tag) : Keyword(loc, tag) {
            Utils::Assert(((int)tag & 0xf80) == 0x180, "not a valid tag");
        }
    };

    struct Identifier : public BaseToken {
        std::string name;

        Identifier(Location loc, std::string name)
            : BaseToken(loc, Tag::Identifier), name(std::move(name)) {}

        void Print() const override { std::cout << "Identifier: " << name; }
    };

    // Literal - includes integer literal and float literal
    struct Literal : public BaseToken {
        ArithValue value;

        Literal(Location loc, ArithValue value)
            : BaseToken(loc, Tag::End), value(value) {
            switch (value.type) {
                case Type::Basic::Int:
                    tag = Tag::IntLiteral;
                    break;
                case Type::Basic::Float:
                    tag = Tag::FloatLiteral;
                    break;
                default:
                    Utils::Err("invalid type");
            }
        }

        void Print() const override {
            std::cout << "Literal: " << (char)value.type;
        }
    };

    // String - char[], will translate to int[] during preprocessing
    struct String : public BaseToken {
        std::string str;

        explicit String(Location loc, std::string str)
            : BaseToken(loc, Tag::String), str(std::move(str)) {}

        void Print() const override { std::cout << "String: " << str; }
    };

    struct EndToken : public BaseToken {
        EndToken(Location loc) : BaseToken(loc, Tag::End) {}
    };

}  // namespace Token
